package djinni

// -RRR | 2023-06-14 | Changes START | To generate .cpp and .hpp files for the ArkTS folder

import djinni.ast._
import djinni.generatorTools._
import djinni.meta.{MExpr, Meta}
import djinni.writer.IndentWriter

import scala.collection.mutable
import scala.util.control.Breaks.{break, breakable}

// -RRR | 2023-08-02 | Changes START | Merge files, to reduce the complexity.
class ArktsCppGenerator(spec: Spec, helperFiles: ArktsHelperFilesDescriptor) extends ARKGenerator(spec, helperFiles) {
  // -RRR | 2023-08-02 | Changes END | Merge files, to reduce the complexity.

  // -RRR | 2023-07-06 | Changes START | To generate hello.cpp file
  var interfacesNames: Array[String] = Array[String]()
  // -RRR | 2023-07-07 | Changes START | To modify hello.cpp file to fix advanced bugs
  var enumNames: Array[(String, List[String])] = Array[(String, List[String])]()
  var enumConstants: List[String] = List[String]()
  var recordNames: Array[String] = Array[String]()

  // -RRR | 2023-08-01 | Changes START | Final modifications as per the latest changes
  val marshalCpp = new CppMarshal(spec)
  val arkTsGenerator = new ArktsGenerator(spec)

  def writeHppFile(name: String, origin: String, includes: Iterable[String], fwds: Iterable[String], f: IndentWriter => Unit, f2: IndentWriter => Unit = (w => {}), isExportHeaderNeeded: Boolean = false) =
    writeHppFileGeneric(spec.arktsOutFolder.get, spec.cppNamespace, spec.cppFileIdentStyle)(name, origin, includes, fwds, f, f2, isExportHeaderNeeded)

  class CppRefs(name: String) {
    var hpp = mutable.TreeSet[String]()
    var hppFwds = mutable.TreeSet[String]()
    var cpp = mutable.TreeSet[String]()

    def find(ty: TypeRef, forwardDeclareOnly: Boolean) {
      find(ty.resolved, forwardDeclareOnly)
    }

    def find(tm: MExpr, forwardDeclareOnly: Boolean) {
      tm.args.foreach((x) => find(x, forwardDeclareOnly))
      find(tm.base, forwardDeclareOnly)
    }

    def find(m: Meta, forwardDeclareOnly: Boolean) = {
      for (r <- marshal.hppReferences(m, name, forwardDeclareOnly)) r match {
        case ImportRef(arg) => hpp.add("#include " + arg)
        case DeclRef(decl, Some(spec.cppNamespace)) => hppFwds.add(decl)
        case DeclRef(_, _) =>
      }
      for (r <- marshal.cppReferences(m, name, forwardDeclareOnly)) r match {
        case ImportRef(arg) => cpp.add("#include " + arg)
        case DeclRef(_, _) =>
      }
    }
  }

  def writeCppTypeParams(w: IndentWriter, params: Seq[TypeParam]) {
    if (params.isEmpty) return
    w.wl("template " + params.map(p => "typename " + idCpp.typeParam(p.ident)).mkString("<", ", ", ">"))
  }

  def getExportMacro(): String = {
    return if (isExportHeaderNeeded()) s"${spec.exportHeaderName.toUpperCase()} " else "";
  }

  def generateHppConstants(w: IndentWriter, consts: Seq[Const]) = {
    for (c <- consts) {
      w.wl
      w.wl(s"static ${marshal.fieldType(c.ty)} const ${idCpp.const(c.ident)};")
    }
  }

  def isExportHeaderNeeded(): Boolean = {
    return spec.exportHeaderName.length > 0;
  }
  // -RRR | 2023-08-01 | Changes END | Final modifications as per the latest changes

  override def generate(idl: Seq[TypeDecl]): Unit = {

    super.generate(idl)

    val fileName = "hello.cpp"
    createFile(spec.arktsOutFolder.get, fileName, { (w: writer.IndentWriter) =>

      w.wl("#include \"napi/native_api.h\"")
      for (iName <- interfacesNames) {
        // -RRR | 2023-07-31 | Changes START | Final modifications as per the latest changes
        var intName = "";
        if (iName.toLowerCase().contains("callback") || iName.toLowerCase().contains("listener")) {
          intName = "\"" + spec.arktsTypePrefix + s"$iName" + ".hpp\""
        }
        else {
          intName = "\"" + spec.arktsTypePrefix + s"$iName" + "Cpp.hpp\""
        }
        // -RRR | 2023-07-31 | Changes END | Final modifications as per the latest changes
        w.wl(s"#include $intName")
      }
      w.wl

      w.wl("EXTERN_C_START")
      w.wl("static napi_value Init(napi_env env, napi_value exports)").braced {
        for (iName <- interfacesNames) {
          if (!iName.toLowerCase().contains("callback")) {
            // -RRR | 2023-07-31 | Changes START | Final modifications as per the latest changes
            val extraInfo = spec.arktsTypePrefix + s"$iName" + "::Init(env, exports);"
            // -RRR | 2023-07-31 | Changes END | Final modifications as per the latest changes
            w.wl(s"$extraInfo")
          }
        }
        w.wl

        w.wl("return exports;")

      }
      w.wl("EXTERN_C_END")
      w.wl

      w.wl("static napi_module callbackModule = ").bracedSemi {
        w.wl(".nm_version = 1,")
        w.wl(".nm_flags = 0,")
        w.wl(".nm_filename = nullptr,")
        w.wl(".nm_register_func = Init,")
        w.wl(".nm_modname = \"entry\",")
        // -RRR | 2023-07-31 | Changes END | Final modifications as per the latest changes
        w.wl(".nm_priv = ((void*)0),")
        w.wl(".reserved = {0},")
      }
      w.wl

      w.wl("extern \"C\" __attribute__((constructor)) void CallbackTestRegister()").braced {
        w.wl("napi_module_register(&callbackModule);")
        // -RRR | 2023-07-07 | Changes END | To modify hello.cpp file to fix advanced bugs
      }
      // -RRR | 2023-07-06 | Changes END | To generate hello.cpp file

    })

  }

  // -RRR | 2023-07-06 | Changes START | To generate hello.cpp file
  override def generateEnum(origin: String, ident: Ident, doc: Doc, e: Enum): Unit = {
    val baseClassName = marshal.typename(ident, e)
    // -RRR | 2023-07-07 | Changes START | To modify hello.cpp file to fix advanced bugs
    enumConstants = enumConstants.take(0)

    for (o <- normalEnumOptions(e)) {
      enumConstants = enumConstants :+ (idJava enum o.ident)
    }

    enumNames = enumNames :+ (baseClassName, enumConstants)
    // -RRR | 2023-07-07 | Changes END | To modify hello.cpp file to fix advanced bugs
  }

  override def generateRecord(origin: String, ident: Ident, doc: Doc, params: Seq[TypeParam], r: Record): Unit = {
    val baseClassName = marshal.typename(ident, r)

    recordNames = recordNames :+ baseClassName
  }
  // -RRR | 2023-07-06 | Changes END | To generate hello.cpp file

  override def generateInterface(origin: String, ident: Ident, doc: Doc, typeParams: Seq[TypeParam], i: Interface): Unit = {

    // -RRR | 2023-06-21 | Changes START | To generate *Cpp.cpp and *Cpp.hpp
    val methodNamesInScope = i.methods.map(m => idCpp.method(m.ident))

    val isNodeMode = false

    //Generate header file
    generateInterface(origin, ident, doc, typeParams, i, isNodeMode)

    //Generate implementation file
    val baseClassName = marshal.typename(ident, i)
    val cppClassName = cppMarshal.typename(ident, i)

    // -RRR | 2023-07-06 | Changes START | To generate hello.cpp file
    interfacesNames = interfacesNames :+ baseClassName
    // -RRR | 2023-07-06 | Changes END | To generate hello.cpp file

    // -RRR | 2023-08-01 | Changes START | Final modifications as per the latest changes
    val refs = new CppRefs(ident.name)

    i.methods.map(m => {
      m.params.map(p => refs.find(p.ty, true))
      m.ret.foreach((x) => refs.find(x, true))
    })
    i.consts.map(c => {
      refs.find(c.ty, true)
    })

    val self = marshal.typename(ident, i)

    //MSVC BUILD: Include export header file so global data symbols will be exported in dll

    if (!refs.hpp.contains("#include <string>")) {
      refs.hpp.add("#include <string>")
    }

    if (refs.hpp.contains("#include <memory>")) {
      refs.hpp.remove("#include <memory>")
    }

    writeHppFile(ident, origin, refs.hpp, refs.hppFwds, w => {

      writeCppTypeParams(w, typeParams)
      val exportMacro = if (i.ext.arkts) getExportMacro() else "";
      w.w(s"class $exportMacro$self").bracedSemi {
        w.wlOutdent("public:")
        // Destructor
        w.wl(s"virtual ~$self() {}")
        // Constants
        generateHppConstants(w, i.consts)
        // Methods
        for (m <- i.methods) {
          w.wl
          writeMethodDoc(w, m, idCpp.local)
          val ret = marshal.returnType(m.ret, methodNamesInScope)
          val params = m.params.map(p => marshal.paramType(p.ty, methodNamesInScope) + " " + idCpp.local(p.ident))
          if (m.static) {
            w.wl(s"static std::shared_ptr<$ret> ${idCpp.method(m.ident)}${params.mkString("(", ", ", ")")};")
          } else {
            val constFlag = if (m.const) " const" else ""
            var params_ = ""
            if (params.mkString("(", ", ", ")").toLowerCase().contains("callback")) {
              params_ = params.mkString("(", ", ", ")")
            } else {
              params_ = params.mkString("(", ", ", ")").replace("const std::shared_ptr<", "").replace(">", "")
            }
            w.wl(s"virtual $ret ${idCpp.method(m.ident)}${params_}$constFlag = 0;")
          }
        }
      }
    },
      w => {},
      isExportHeaderNeeded())

    // -RRR | 2023-06-20 | Changes START | To generate *.cpp and *.hpp files for Enums and Records
    if (i.ext.cpp) {
      // -RRR | 2023-06-20 | Changes END | To generate *.cpp and *.hpp files for Enums and Records

      // -RRR | 2023-07-05 | Changes START | To disbale generating *Cpp.cpp and *Cpp.hpp files

      val prefixBaseClassName = spec.arktsTypePrefix + baseClassName

      val fileName = spec.arktsTypePrefix + idNode.ty(ident.name) + "Cpp.cpp"
      createFile(spec.arktsOutFolder.get, fileName, { (w: writer.IndentWriter) =>
        w.wl("// AUTOGENERATED FILE - DO NOT MODIFY!")
        w.wl("// This file generated by Djinni from " + origin)
        w.wl
        //        w.wl("#include \"napi/native_api.h\"")
        val hppF = "#include \"" + spec.arktsTypePrefix + idNode.ty(ident.name) + "Cpp.hpp" + "\""
        w.wl(s"$hppF")
        val hppFileName = "#include \"" + idNode.ty(ident.name) + "." + spec.cppHeaderExt + "\""
        w.wl(hppFileName)
        w.wl
        w.wl("#include \"common.h\"")
        w.wl("#include <string>")
        w.wl
        w.wl("#include <hilog/log.h>")

        w.wl
        w.wl(s"napi_ref $prefixBaseClassName::listener_ref;")
        w.wl(s"napi_ref $prefixBaseClassName::constructor;")
        w.wl(s"napi_threadsafe_function $prefixBaseClassName::tsfn;")
        w.wl

        w.wl("struct promiseInfo").bracedSemi {
          w.wl("napi_async_work worker;")
          w.wl("napi_deferred deferred;")
          w.wl
          w.wl("string from;")
          w.wl("string to;")
          w.wl("int32_t sayType;")
          w.wl
          w.wl("int32_t result;")
          w.wl("string errMsg;")
          w.wl("string response;")
        }

        w.wl
        w.wl("struct promiseInfo dataInstance = {nullptr, nullptr, \"\", \"\", 0, 0, \"\", \"\"};")
        w.wl("struct promiseInfo *ptr = &dataInstance;")
        w.wl

        w.wl("void work(napi_env env, void *data)").braced {
          w.wl("struct promiseInfo *arg = (struct promiseInfo *)data;")
          w.wl("int sum = 100;")
          w.wl("arg->result = sum;")
          w.wl("arg->errMsg = \"native error\";")
          w.wl("arg->response = \"native response\";")
        }
        w.wl

        w.wl("void done(napi_env env, napi_status status, void *data)").braced {
          w.wl("struct promiseInfo *arg = (struct promiseInfo *)data;")
          w.wl("OH_LOG_INFO(LOG_APP, \"Test C++ DONE Resolve Promise number: %{public}d\", arg->result);")
          w.wl("napi_value ret;")
          w.wl("napi_create_int32(env, arg->result, &ret);")
          w.wl("napi_resolve_deferred(env, arg->deferred, ret);")
          w.wl("napi_delete_async_work(env, arg->worker);")
          w.wl("arg->deferred = nullptr;")
        }

        var factory: Option[Interface.Method] = None
        var factoryFound = false
        for (m <- i.methods) {
          if (!m.static) {
            val methodName = m.ident.name
            w.w(s"napi_value $prefixBaseClassName::$methodName(napi_env env, napi_callback_info info)").braced {

              w.wl("// Check if method called with right number of arguments")
              w.wl

              // Check if we have Callback or ListCallback as argument
              var args = ""
              var resolver = ""
              var hasCallback = false
              m.params.foreach(p => {
                val index = m.params.indexOf(p)

                args = s"${args}args[$index]"
                if (p != m.params.last) {
                  args = s"${args},"
                }

                if (p.ty.expr.ident.name.contains("Callback") ||
                  p.ty.expr.ident.name.contains("ListCallback")) {
                  resolver = s"arg_${index}_resolver"
                  hasCallback = true
                }
              })

              val argsLength = if (hasCallback) m.params.length - 1 else m.params.length

              w.wl

              w.wl(s"size_t argc = $argsLength;")
              if (argsLength == 1) {
                w.wl(s"napi_value argv[$argsLength] = {0};")
              } else {
                w.wl(s"napi_value argv[$argsLength];")
              }
              w.wl("napi_value thisVar = nullptr;")
              w.wl("void *data = nullptr;")
              w.wl("NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, &data));")
              val classMethodName = "\"" + prefixBaseClassName + "::" + methodName + " needs " + argsLength + " arguments\""
              w.wl(s"NODE_API_ASSERT(env, argc == $argsLength, $classMethodName);")
              w.wl

              val params = m.params.map(p => marshal.paramType(p.ty, methodNamesInScope) + " " + idCpp.local(p.ident))

              w.wl("// Check if parameters have correct types")
              if (methodName.toLowerCase().contains("add")) {
                for (n <- 0 until argsLength) {
                  w.wl(s"napi_valuetype valuetype$n;")
                  w.wl(s"NODE_API_CALL(env, napi_typeof(env, argv[$n], &valuetype$n));")
                  val str = "\"" + "Wrong type of arguments. Expects an object as Position- " + n + " argument.\""
                  w.wl(s"NODE_API_ASSERT(env, valuetype$n == napi_object, $str);")
                  w.wl
                  val params_ = params.mkString("(", ", ", ")")
                    .replace("(const std::shared_ptr<", "")
                    .replace("> & listener)", "")
                  w.wl(s"$params_ *argv_$n;")
                  w.wl(s"NODE_API_CALL(env, napi_unwrap(env, argv[$n], reinterpret_cast<void **>(&argv_$n)));")
                  val classMethodName4 = "\"" + "NodeJs Object to " + spec.arktsTypePrefix + params_ + " failed\""
                  w.wl(s"NODE_API_ASSERT(env, argv_$n, $classMethodName4);")
                  w.wl
                }
              } else if (methodName.toLowerCase().contains("remove")) {
                for (n <- 0 until argsLength) {
                  val params_ = params.mkString("(", ", ", ")")
                    .replace("(const std::shared_ptr<", "")
                    .replace("> & listener)", "")
                  w.wl(s"$params_ *argv_$n;")
                  w.wl(s"NODE_API_CALL(env, napi_unwrap(env, argv[$n], reinterpret_cast<void **>(&argv_$n)));")
                  val classMethodName3 = "\"" + prefixBaseClassName + "::" + methodName + " : NodeJs Object to " + spec.arktsTypePrefix + params_ + " failed\""
                  w.wl(s"NODE_API_ASSERT(env, argv_$n,\n\t\t$classMethodName3);")
                  w.wl
                }
              }
              else {
                var n = 0
                for (par <- params) {
                  if (!par.toLowerCase().contains("callback")) {
                    w.wl(s"napi_valuetype valuetype$n;")
                    w.wl(s"NODE_API_CALL(env, napi_typeof(env, argv[$n], &valuetype$n));")
                    var str = ""
                    if (par.toLowerCase().contains("string")) {
                      str = "\"" + "Wrong type of arguments. Expects a string as Position- " + n + " argument.\""
                      w.wl(s"NODE_API_ASSERT(env, valuetype$n == napi_string, $str);")
                    } else {
                      str = "\"" + "Wrong type of arguments. Expects an enum as Position- " + n + " argument.\""
                      w.wl(s"NODE_API_ASSERT(env, valuetype$n == napi_number, $str);")
                    }
                    w.wl
                    n = n + 1
                  }
                }
                var num = 0
                for (par <- params) {
                  if (!par.toLowerCase().contains("callback")) {
                    if (par.toLowerCase().contains("string")) {
                      val additionalString = "\"\""
                      w.wl(s"char argv_$num[256] = $additionalString;")
                      num = num + 1
                    } else {
                      w.wl(s"int32_t argv_$num;")
                      num = num + 1
                    }
                  }
                }
                w.wl("size_t value_length;")
                var num2 = 0
                for (par <- params) {
                  if (!par.toLowerCase().contains("callback")) {
                    if (par.toLowerCase().contains("string")) {
                      w.wl(s"NODE_API_CALL(env, napi_get_value_string_utf8(env, argv[$num2], argv_$num2, 255, &value_length));")
                      num2 = num2 + 1
                    } else {
                      w.wl(s"NODE_API_CALL(env, napi_get_value_int32(env, argv[$num2], &argv_$num2));")
                      num2 = num2 + 1
                    }
                  }
                }

              }

              var bool_flag = false

              breakable {
                for (par <- params) {
                  if (par.toLowerCase().contains("callback")) {
                    bool_flag = true
                    break
                  } else {
                    bool_flag = false
                  }
                }
              }

              if (!bool_flag) {
                w.wl
                w.wl("// Unwrap current object and retrieve its Cpp Implementation")
                w.wl(s"$baseClassName *cpp_impl;")
                w.wl("NODE_API_CALL(env, napi_unwrap(env, thisVar, reinterpret_cast<void **>(&cpp_impl)));")
                val classMethodName2 = "\"" + prefixBaseClassName + "::" + methodName + " : implementation of " + baseClassName + " is not valid\""
                w.wl(s"NODE_API_ASSERT(env, cpp_impl, $classMethodName2);")
                w.wl("return nullptr;")
                w.wl
              } else {
                w.wl("// Create promise and set it into Callback")
                var num = 0
                for (par <- params) {
                  if (!par.toLowerCase().contains("callback")) {
                    var par_ = par.replace("const std::string & ", "").replace("SayType ", "")
                    w.wl(s"ptr->$par_ = argv_$num;")
                    num = num + 1
                  }
                }

                w.wl("napi_value promise;")
                w.wl("if (dataInstance.deferred != NULL)").braced {
                  w.wl("return NULL;")
                }
                w.wl
                w.wl("NODE_API_CALL(env, napi_create_promise(env, &ptr->deferred, &promise));")
                w.wl("napi_value resource_name;")
                var str_ = "\"" + methodName + "\""
                w.wl(s"napi_create_string_utf8(env, $str_, NAPI_AUTO_LENGTH, &resource_name);")
                w.wl("napi_create_async_work(env, nullptr, resource_name, work, done, ptr, &ptr->worker);")
                w.wl("napi_queue_async_work(env, ptr->worker);")
                w.wl("return promise;")
              }

            }
            // Get factory method if it exists (will be used for Nan::New method)
            if (!factoryFound && m.static) {
              factoryFound = m.ret.exists { x =>
                val returnTypeName = cppMarshal.paramType(x.resolved, true)
                returnTypeName.equals(s"std::shared_ptr<$cppClassName>")
              }
              if (factoryFound) {
                factory = Some(m)
              }
            }
          }
        }

        w.wl(s"void $prefixBaseClassName::Destructor(napi_env env, void *nativeObject, void * /*finalize_hint*/)").braced {
          w.wl(s"$prefixBaseClassName *obj = static_cast<$prefixBaseClassName *>(nativeObject);")
          w.wl("obj = NULL;")
          w.wl("return;")
        }

        w.wl
        createWrapMethod(ident, i, w)
        w.wl
        createInitializeMethod(ident, i, w)
        w.wl
        createNanNewMethod(ident, i, None, w)
        w.wl
      }
      )
    }
  }

  def createCheckMethod(ident: Ident, i: Interface, wr: writer.IndentWriter): Unit = {
    val baseClassName = marshal.typename(ident, i)
    val cppClassName = cppMarshal.typename(ident, i)
    wr.w(s"NAN_METHOD($baseClassName::isNull)").braced {
      wr.wl(s"auto cpp_implementation = djinni::js::ObjectWrapper<${spec.cppNamespace}::$cppClassName>::Unwrap(info.This());")
      wr.wl("auto isNull = !cpp_implementation ? true : false;")
      wr.wl("return info.GetReturnValue().Set(Nan::New<Boolean>(isNull));")
    }
  }
}

// -RRR | 2023-06-14 | Changes END | To generate .cpp and .hpp files for the ArkTS folder